/**
 * MIT License
 * 
 * Copyright (c) 2017 CaiDongyu
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.tunnel.server.tunnel;
  
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;

import com.tunnel.common.constant.Constant;
import com.tunnel.common.nio.EventLoopGroupManager;
import com.tunnel.common.tunnel.TunnelBaseHandler;
import com.tunnel.common.tunnel.TunnelDataQueue;
import com.tunnel.common.tunnel.TunnelDataSenderManager;
import com.tunnel.common.util.ByteArrayUtil;
import com.tunnel.common.util.CollectionUtil;
import com.tunnel.common.util.LogUtil;
import com.tunnel.common.util.StringUtil;
import com.tunnel.server.http.HttpChannelManager;
import com.tunnel.server.http.HttpChannelManager.HttpChannelContext;
import com.tunnel.server.http.HttpClientManager;
import com.tunnel.server.http.HttpClientManager.HttpClient;
import com.tunnel.server.tcp.TcpChannelManager;
import com.tunnel.server.tcp.TcpChannelManager.TcpChannelContext;
import com.tunnel.server.tcp.TcpClientManager;
import com.tunnel.server.tcp.TcpClientManager.TcpClient;
import com.tunnel.server.tcp.TcpServer;
import com.tunnel.server.udp.UdpClientManager;
import com.tunnel.server.udp.UdpClientManager.UdpClient;
import com.tunnel.server.udp.UdpServer;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;  
  
public class TunnelServerHandler extends TunnelBaseHandler{
	public TunnelServerHandler() {
		super("Tunnel-Server");
	}

	public static final AttributeKey<String> CLIEN_HOSTARY_KEY = AttributeKey.valueOf("client.hostAry"); 
	public static final AttributeKey<String> CLIEN_TCPARY_KEY = AttributeKey.valueOf("client.tcpAry"); 
	public static final AttributeKey<String> CLIEN_UDPARY_KEY = AttributeKey.valueOf("client.udpAry"); 
    

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		//添加ctx到消息队列的心跳列表
		TunnelDataQueue tunnelDataQueue = new TunnelDataQueue("Tunnel-Server",ctx,true);
		TunnelDataSenderManager.addQueue(tunnelDataQueue);
		tunnelDataQueue.startQueue();
		super.channelActive(ctx);
	}

	@Override
	protected void handleData(ChannelHandlerContext ctx, ByteBuf buf, byte flag) {
		switch (flag) {
			case TunnelDataSenderManager.PING_MSG:
//				LogUtil.d("Tunnel心跳");
				break;
				
			//注册
			case TunnelDataSenderManager.REGISTER_MSG:
				registerMsg(ctx,buf);
				break;
				
			//HTTP
			case TunnelDataSenderManager.HTTP_DATA_MSG:
				httpDataMsg(ctx, buf);
				break;
				
			//TCP
			case TunnelDataSenderManager.TCP_DATA_MSG:
				tcpDataMsg(ctx, buf);
				break;
			case TunnelDataSenderManager.TCP_CLOSE_MSG:
				tcpCloseMsg(ctx, buf);
				break;
			
			//UDP
			case TunnelDataSenderManager.UDP_DATA_MSG:
				udpDataMsg(ctx, buf);
				break;
			default:
				break;
		}
	}
	

    private void registerMsg(ChannelHandlerContext ctx, ByteBuf buf) {
    	//客户端来的注册信息
		byte[] data = new byte[buf.readableBytes()];
    	buf.getBytes(buf.readerIndex(), data,0,data.length);
        String content = new String(data);
		if (StringUtil.isNotEmpty(content) || content.contains(Constant.SPLIT_FLAG)) {
			String[] split = content.split(Constant.SPLIT_FLAG);
			String clientName = split[0];

			//返回信息
			StringBuilder sb = new StringBuilder();
			sb.append("\r\n\r\n[>>>> REGISTER RESULT <<<<]\r\n");
			
			//############ HTTP ###############
			if(split.length>=2){
				String hostAry = split[1];
				List<String> successHostList = new ArrayList<>();
				List<String> failedHostList = new ArrayList<>();
				registerHostAry(clientName, hostAry, successHostList, failedHostList, ctx);
				
				//成功注册的域名
				if(CollectionUtil.isNotEmpty(successHostList)){
					sb.append("HOST NAME REGISTER SUCCESS:\r\n");
					for (int i=0;i<successHostList.size();i++) {
						sb.append(i+1)
						.append(". ")
						.append(successHostList.get(i))
						.append("\r\n");
					}
					sb.append("\r\n");
				}
				//注册失败的域名
				if(CollectionUtil.isNotEmpty(failedHostList)){
					sb.append("HOST NAME REGISTER FAILED:\r\n");
					for (int i=0;i<failedHostList.size();i++) {
						sb.append(i+1)
						.append(". ")
						.append(failedHostList.get(i))
						.append("\r\n");
					}
					sb.append("\r\n");
				}
			}
			//################ TCP ######################
			if(split.length>=3){
				String tcpAry = split[2];
				List<String> successTcpList = new ArrayList<>();
				List<String> failedTcpList = new ArrayList<>();
				registerTcpAry(clientName, tcpAry, successTcpList, failedTcpList, ctx);
				
				//成功注册的TCP端口
				if(CollectionUtil.isNotEmpty(successTcpList)){
					sb.append("TCP PORT REGISTER SUCCESS:\r\n");
					for (int i=0;i<successTcpList.size();i++) {
						sb.append(i+1)
						.append(". ")
						.append(successTcpList.get(i))
						.append("\r\n");
					}
					sb.append("\r\n");
				}
				//注册失败的TCP端口
				if(CollectionUtil.isNotEmpty(failedTcpList)){
					sb.append("TCP PORT REGISTER FAILED:\r\n");
					for (int i=0;i<failedTcpList.size();i++) {
						sb.append(i+1)
						.append(". ")
						.append(failedTcpList.get(i))
						.append("\r\n");
					}
					sb.append("\r\n");
				}
			}
			//################ UDP ######################
			if(split.length>=4){
				String udpAry = split[3];
				List<String> successUdpList = new ArrayList<>();
				List<String> failedUdpList = new ArrayList<>();
				registerUdpAry(clientName, udpAry, successUdpList, failedUdpList, ctx);
				
				//成功注册的UDP端口
				if(CollectionUtil.isNotEmpty(successUdpList)){
					sb.append("UDP PORT REGISTER SUCCESS:\r\n");
					for (int i=0;i<successUdpList.size();i++) {
						sb.append(i+1)
						.append(". ")
						.append(successUdpList.get(i))
						.append("\r\n");
					}
					sb.append("\r\n");
				}
				//注册失败的UDP端口
				if(CollectionUtil.isNotEmpty(failedUdpList)){
					sb.append("UDP PORT REGISTER FAILED:\r\n");
					for (int i=0;i<failedUdpList.size();i++) {
						sb.append(i+1)
						.append(". ")
						.append(failedUdpList.get(i))
						.append("\r\n");
					}
					sb.append("\r\n");
				}
			}
			
			TunnelDataSenderManager.commitData(TunnelDataSenderManager.REGISTER_MSG, ctx, sb.toString().getBytes());
			
		}
	}
    
	private void registerHostAry(String clientName,String hostAry,List<String> successList,List<String> failedList,ChannelHandlerContext ctx){
		if(!Constant.NULL.equals(hostAry)){
			//如果客户的域名端登记注册成功
			//就把这个ctx里，记上对应的域名，因为在ctx异常或者关闭时候，tunnel是要被销毁的
			//tunnel的销毁，就靠ctx携带的域名来找
			Attribute<String> hostAryAttr = ctx.attr(CLIEN_HOSTARY_KEY);
			if(hostAryAttr.get() == null){
				hostAryAttr.setIfAbsent(hostAry);
			}
			String[] split = hostAry.split(",");
			int hostIndex = 0;
			for (String host : split) {
				if(successList.size() <= Byte.MAX_VALUE){
					//限制客户端注册的域名列表数，因为必须下标能在一个字节的寻址范围内
					host = host == null ? "" : host.trim();
					HttpClient tunnel = new HttpClient(host, clientName, hostIndex++);
					tunnel.setChannelHandlerContext(ctx);
					boolean ok = HttpClientManager.add(tunnel);
					if (ok) {
						successList.add(host);
					} else {
						failedList.add(host);
					}
				}else{
					failedList.add(host);
				}
			}
		}
    }

    private void registerTcpAry(String clientName, String tcpAry, List<String> successList,List<String> failedList, ChannelHandlerContext ctx) {
    	if(!Constant.NULL.equals(tcpAry)){
    		String[] split = tcpAry.split(",");
    		int portIndex = 0;
    		for (String port : split) {
    			if(successList.size() <= Byte.MAX_VALUE){
    				//限制客户端注册的域名列表数，因为必须下标能在一个字节的寻址范围内
    				port = port == null ? "" : port.trim();
    				TcpClient tunnel = new TcpClient(Integer.valueOf(port), clientName, portIndex);
    				tunnel.setChannelHandlerContext(ctx);
    				boolean ok = TcpClientManager.add(tunnel);
    				if (ok) {
    					//启动对应的tcp端口监听
    					try {
							new TcpServer(Integer.valueOf(port),portIndex).start();
							successList.add(port);
						} catch (Exception e) {
							LogUtil.e(e);
							failedList.add(port+" error:"+e.getMessage());
							TcpClientManager.remove(Integer.valueOf(port));
						}
    				} else {
    					failedList.add(port);
    				}
    			}else{
    				failedList.add(port);
    			}
    			portIndex++;
    		}

    		//如果登记注册成功
    		//就把这个ctx里，记上对应的注册端口列表，因为在ctx异常或者关闭时候，tunnel是要被销毁的
    		//tunnel的销毁，就靠ctx携带的端口列表来找
    		Attribute<String> tcptAryAttr = ctx.attr(CLIEN_TCPARY_KEY);
    		if(tcptAryAttr.get() == null && CollectionUtil.isNotEmpty(successList)){
    			//只保存成功注册的port
    			tcptAryAttr.setIfAbsent(String.join(",", successList));
    		}
    	}
	}
    
    private void registerUdpAry(String clientName, String udpAry, List<String> successList,List<String> failedList, ChannelHandlerContext ctx) {
    	if(!Constant.NULL.equals(udpAry)){
    		String[] split = udpAry.split(",");
    		int portIndex = 0;
    		for (String port : split) {
    			if(successList.size() <= Byte.MAX_VALUE){
    				//限制客户端注册的域名列表数，因为必须下标能在一个字节的寻址范围内
    				port = port == null ? "" : port.trim();
    				UdpClient tunnel = new UdpClient(Integer.valueOf(port), clientName, portIndex);
    				tunnel.setChannelHandlerContext(ctx);
    				boolean ok = UdpClientManager.add(tunnel);
    				if (ok) {
    					//启动对应的udp端口监听
    					try {
							new UdpServer(Integer.valueOf(port),portIndex).start();
							successList.add(port);
						} catch (Exception e) {
							LogUtil.e(e);
							failedList.add(port+" error:"+e.getMessage());
							UdpClientManager.remove(Integer.valueOf(port));
						}
    				} else {
    					failedList.add(port);
    				}
    			}else{
    				failedList.add(port);
    			}
    			portIndex++;
    		}
    		
    		//如果登记注册成功
    		//就把这个ctx里，记上对应的注册端口列表，因为在ctx异常或者关闭时候，tunnel是要被销毁的
    		//tunnel的销毁，就靠ctx携带的端口列表来找
    		Attribute<String> udptAryAttr = ctx.attr(CLIEN_UDPARY_KEY);
    		if(udptAryAttr.get() == null && CollectionUtil.isNotEmpty(successList)){
    			//只保存成功注册的port
    			udptAryAttr.setIfAbsent(String.join(",", successList));
    		}
    	}
	}

	private void httpDataMsg(ChannelHandlerContext ctx, ByteBuf buf){
		//前16个字符是
		//requestId 时间戳+三位随机数，代表http请求的编号 占16位
		if(buf.readableBytes() > 16){
			byte[] requestIdBytes = new byte[16];
			buf.getBytes(buf.readerIndex(), requestIdBytes, 0, 16);
			
			byte[] dataBytes = new byte[buf.readableBytes()-16];
			buf.getBytes(buf.readerIndex()+16, dataBytes, 0, dataBytes.length);
			
			byte[] httpEndFlagBytes = new byte[Constant.HTTP_RESPONSE_END_FLAG_BYTES.length];
			boolean httpEnd = false;
			if(dataBytes.length >= Constant.HTTP_RESPONSE_END_FLAG_BYTES.length){
				System.arraycopy(dataBytes, dataBytes.length-Constant.HTTP_RESPONSE_END_FLAG_BYTES.length, httpEndFlagBytes, 0, Constant.HTTP_RESPONSE_END_FLAG_BYTES.length);
				String httpEndFlag = new String(httpEndFlagBytes);
				if(httpEndFlag.equals(Constant.HTTP_RESPONSE_END_FLAG)){
					httpEnd = true;
					//说明结束了，去掉结束的表示数据
					byte[] newDataBytes = new byte[dataBytes.length-Constant.HTTP_RESPONSE_END_FLAG_BYTES.length];
					System.arraycopy(dataBytes, 0, newDataBytes, 0, newDataBytes.length);
					dataBytes = newDataBytes;
				}
			}
			
			//解析完数据接口
			//开始查找浏览器连接，回传tunnel-client给过来的数据
			HttpChannelContext httpCtx = HttpChannelManager.get(new String(requestIdBytes));
			if(httpCtx != null){
				if(dataBytes.length > 0){
//							LogUtil.d("tunnel-server返回: "+new String(dataBytes));

	                //netty channel里发送数据是都堆在堆内存里的，空间限制，因此如果写的太快，能回oom
	                int waiteMs = 1;
	                while(!ctx.channel().isWritable()){
	                	try {
	                		Thread.sleep(waiteMs);
						} catch (Exception e) {}
	                	waiteMs = waiteMs+1;
	                }
					
					ChannelFuture future = httpCtx.getChannel().writeAndFlush(dataBytes);
					httpCtx.setChannelFuture(future);
				}
				
				if(httpEnd && httpCtx.getChannelFuture() != null){
					httpCtx.getChannelFuture().addListener(ChannelFutureListener.CLOSE);
				}
			}
		}
	}
	
	private void tcpDataMsg(ChannelHandlerContext ctx, ByteBuf buf){
		//前16个字符是
		//connectionId 时间戳+三位随机数，代表http请求的编号 占16位
		if(buf.readableBytes() > 16){
			byte[] connectionIdBytes = new byte[16];
			buf.getBytes(buf.readerIndex(), connectionIdBytes, 0, 16);
			
			byte[] dataBytes = new byte[buf.readableBytes()-16];
			if(dataBytes.length > 0){
				buf.getBytes(buf.readerIndex()+16, dataBytes, 0, dataBytes.length);
			}
			TcpChannelContext tcpCtx = TcpChannelManager.get(new String(connectionIdBytes));
			if(tcpCtx != null){
				//这里不同于httpServerHandler
				//TcpServer没有Http解析器，他只识别管道中的ByteBuf数据，byte[]数据是无法发送出去的
//				LogUtil.d(new String(dataBytes));
//				ByteBuf buffer = ctx.alloc().buffer();
//		        buffer.writeBytes(dataBytes);

                //netty channel里发送数据是都堆在堆内存里的，空间限制，因此如果写的太快，能回oom
                int waiteMs = 1;
                while(!ctx.channel().isWritable()){
                	try {
                		Thread.sleep(waiteMs);
					} catch (Exception e) {}
                	waiteMs = waiteMs+1;
                }
                
				tcpCtx.getChannel().writeAndFlush(dataBytes);
			}
		}
	}
	
	private void tcpCloseMsg(ChannelHandlerContext ctx, ByteBuf buf){
		//前16个字符是
		//connectionId 时间戳+三位随机数，代表http请求的编号 占16位
		if(buf.readableBytes() >= 16){
			byte[] connectionIdBytes = new byte[16];
			buf.getBytes(buf.readerIndex(), connectionIdBytes, 0, 16);
			
			byte[] dataBytes = new byte[buf.readableBytes()-16];
			if(dataBytes.length > 0){
				buf.getBytes(buf.readerIndex()+16, dataBytes, 0, dataBytes.length);
			}
			//关闭tcp连接
			TcpChannelContext tcpCtx = TcpChannelManager.get(new String(connectionIdBytes));
			if(tcpCtx != null){
				//这里不同于httpServerHandler
				//TcpServer没有Http解析器，他只识别管道中的ByteBuf数据，byte[]数据是无法发送出去的
//				LogUtil.d(new String(dataBytes));
//				ByteBuf buffer = ctx.alloc().buffer();
//		        buffer.writeBytes(dataBytes);

                //netty channel里发送数据是都堆在堆内存里的，空间限制，因此如果写的太快，能回oom
                int waiteMs = 1;
                while(!ctx.channel().isWritable()){
                	try {
                		Thread.sleep(waiteMs);
					} catch (Exception e) {}
                	waiteMs = waiteMs+1;
                }
                
				tcpCtx.getChannel().writeAndFlush(dataBytes).addListener(ChannelFutureListener.CLOSE);
			}
		}
	}
	
	private void udpDataMsg(ChannelHandlerContext ctx, ByteBuf buf){
		if(buf.readableBytes() > 1){
			//发送者的地址+分隔符+发送者的端口(占多位)+分隔符+数据
			byte[] sourceBytes = new byte[buf.readableBytes()];
			buf.getBytes(buf.readerIndex(), sourceBytes, 0, sourceBytes.length);
			//第一次分割，得到发送者地址
			byte[][] splitBytes = ByteArrayUtil.splitOnce(sourceBytes, Constant.SPLIT_FLAG_BYTES);
			byte[] senderAddressBytes = splitBytes[0];
			//第二次分割，得到发送者端口和数据
			splitBytes = ByteArrayUtil.splitOnce(splitBytes[1], Constant.SPLIT_FLAG_BYTES);
			byte[] senderPort = splitBytes[0];
			byte[] dataBytes = splitBytes[1];
			
//			LogUtil.d(String.valueOf(portIndexBytes[0]));
//			LogUtil.d(new String(senderAddressBytes));
//			LogUtil.d(new String(senderPort));
//			LogUtil.d(new String(dataBytes));
			
			
			DatagramSocket client = null;
	    	try {
	    		do{
	    			client = new DatagramSocket();
	                //封装要发送目标的地址
	    			InetAddress senderAddress = InetAddress.getByAddress(senderAddressBytes);
	    			LogUtil.d(senderAddress.getHostAddress()+":"+Integer.valueOf(new String(senderPort)));
	                //封装要发送的DatagramPacket的对象，由于要发送到目的主机，所以要加上地址和端口号
	                DatagramPacket sendPacket = new DatagramPacket(dataBytes,dataBytes.length,senderAddress,Integer.valueOf(new String(senderPort)));

	                try {
	                    //发送数据
	                    client.send(sendPacket);
	                }catch (Exception e){
	                    e.printStackTrace();
	                }
	    			
	    		}while(false);
			} catch (Exception e) {
				LogUtil.e(e);
				try {
					if(client != null){
						client.close();
					}
				} catch (Exception e2) {}
			}
		}
			
	}
	
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        LogUtil.d("Tunnel-Server tunnel exception");
		LogUtil.e(cause);
        ctx.close();
    }
	
	@Override
    protected void handleReaderIdle(ChannelHandlerContext ctx) {
        LogUtil.d("Tunnel-Server tunnel timeout");
        super.handleReaderIdle(ctx);
        ctx.close();
    }
    
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    	super.channelInactive(ctx);
    	
    	LogUtil.d("Tunnel-Server tunnel closing");
		try {
			//关闭消息队列
	        LogUtil.d("closing data queue");
			TunnelDataSenderManager.closeQueue(ctx);
		} catch (Exception e) {
			LogUtil.e(e);
		}
		
    	try {
	        LogUtil.d("stopping http srever handler & removing http client");
    		Attribute<String> attr = ctx.attr(CLIEN_HOSTARY_KEY);
            if(attr.get() != null){
            	String hosts = attr.get();
            	String[] hostAry = hosts.split(",");
            	for(String host:hostAry){
        	        List<HttpChannelContext> list = HttpChannelManager.getList(host);
        	        for(HttpChannelContext httpServer:list){
        	        	//这里关闭channel，也就是HttpServer正在处理的连接
        	        	httpServer.getChannel().close();
        	        }
            		HttpClientManager.remove(host);
            	}
            }
		} catch (Exception e)  {
			LogUtil.e(e);
		}

    	try {
	        LogUtil.d("stopping tcp srever handler & removing tcp client");
    		Attribute<String> attr = ctx.attr(CLIEN_TCPARY_KEY);
            if(attr.get() != null){
            	String tcps = attr.get();
            	String[] tcpAry = tcps.split(",");
            	for(String port:tcpAry){
        			//关闭netty
            		EventLoopGroupManager.shutdown(Integer.valueOf(port));
            		
        			TcpClientManager.remove(Integer.valueOf(port));
            	}
            }
		} catch (Exception e) {
			LogUtil.e(e);
		}
    	
    	try {
	        LogUtil.d("stopping udp srever handler & removing udp client");
    		Attribute<String> attr = ctx.attr(CLIEN_UDPARY_KEY);
            if(attr.get() != null){
            	String udps = attr.get();
            	String[] udpAry = udps.split(",");
            	for(String port:udpAry){
        			//关闭netty
            		EventLoopGroupManager.shutdown(Integer.valueOf(port));
            		
        			UdpClientManager.remove(Integer.valueOf(port));
            	}
            }
		} catch (Exception e) {
			LogUtil.e(e);
		}
        LogUtil.d("Tunnel-Server tunnel close success");
    }
    

}  